package ru.gerunn.parsing.service;

import java.io.UnsupportedEncodingException;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.hibernate.Query;
import org.hibernate.Session;

import ru.gerunn.parsing.tables.DefaultLog;
import ru.gerunn.parsing.tables.DefaultOperation;
import ru.gerunn.parsing.tables.DefaultType;
import ru.gerunn.util.UTF8Truncater;

public class LogParser<Type extends DefaultType, Operation extends DefaultOperation<Type>, Log extends DefaultLog<Operation>> implements DBParser {
	
	private Timestamp timestamp = new Timestamp(0);
	private Class<Type> typeClass;
	private Class<Operation> operationClass;
	private Class<Log> logClass;
	private Type type;
	private String name;
	private Session session;
	private Pattern pattern;
	private String string;
	private Map<String, Operation> operations = new HashMap<String, Operation>();
	
	public LogParser(Session session, String name, Pattern pattern, Class<Type> typeClass, Class<Operation> operationClass, Class<Log> logClass) {
		this.session = session;
		this.name = name;
		this.pattern = pattern;
		this.typeClass = typeClass;
		this.operationClass = operationClass;
		this.logClass = logClass;
		
		this.init();
	}
	
	@SuppressWarnings("unchecked")
	private void init() {
		Query query = this.session.createQuery("from " + typeClass.getName() + " where name = ?");
		query.setString(0, this.name);
		this.session.beginTransaction();
		List<Type> types = (List<Type>) query.list();
		this.session.getTransaction().commit();
		
		try {
			if (types.size() == 0) {
				Type type = this.typeClass.newInstance();
				type.setName(this.name);
				this.session.beginTransaction();
				this.session.save(type);
				types = (List<Type>) query.list();
				this.session.getTransaction().commit();
				this.type = types.get(0);
			} else {
				this.type = types.get(0);
				
				if (this.type != null) {
					query = this.session.createQuery("from " + this.logClass.getName() + " where operation in (select id from " + this.operationClass.getName() + " where type = ?) order by id desc");
					query.setLong(0, this.type.getId());
					query.setMaxResults(1);
					this.session.beginTransaction();
					List<Log> logs = (List<Log>) query.list();
					this.session.getTransaction().commit();
					
					if (logs.size() > 0) {
						this.timestamp = logs.get(0).getExecuted();
					}
				}
			}
			
			query = this.session.createQuery("from " + operationClass.getName());
			this.session.beginTransaction();
			List<Operation> operations = (List<Operation>) query.list();
			this.session.getTransaction().commit();
			
			for (Operation operation : operations) {
				try {
					this.operations.put(new String(operation.getName(), 0, operation.getName().length, "UTF-8"), operation);
				} catch (UnsupportedEncodingException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	
	public void setString(String string) {
		this.string = string;
	}

	public void parse() {
		try {
			Matcher matcher = this.pattern.matcher(this.string);
			if (matcher.matches()) {
				String executed = matcher.group(1);
				long duration = (long) Integer.parseInt(matcher.group(2));
				String operationNameString = matcher.group(3);
				byte[] operationName = operationNameString.getBytes();
				String operationNameCutted = matcher.group(3);
				int operationNameLength = 255;
				if (operationNameCutted.length() > operationNameLength) {
					operationNameCutted = UTF8Truncater.truncate(operationNameCutted, operationNameLength);
				}
				byte[] params = matcher.group(4).getBytes();
				String paramsCutted = matcher.group(4);
				int paramsLength = 500;
				if (paramsCutted.length() > paramsLength) {
					paramsCutted = UTF8Truncater.truncate(paramsCutted, paramsLength);
				}

				try {
					if (duration > 500) {
						Operation operation = this.operations.get(operationNameString);
						if (operation == null) {
							System.out.println(operationNameString);
							operation = operationClass.newInstance();
							operation.setName(operationName);
							operation.setNameCutted(operationNameCutted);
							operation.setType(this.type);
							this.addOperation(operation);
						}
						
						Log log = logClass.newInstance();
						log.setDuration(duration);
						log.setOperation(operation);
						log.setParams(params);
						log.setParamsCutted(paramsCutted);
		
						log.setExecuted(new Timestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(executed).getTime()));

						if (this.timestamp.compareTo(log.getExecuted()) < 0) {
							this.addLog(log);
						}
					}
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				} catch (ParseException e) {
					e.printStackTrace();
				}
				
			} else {
				if (this.string.contains(name)) {
					System.out.println(this.string);
				}
			}
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	private void addOperation(Operation operation) throws UnsupportedEncodingException {
		this.session.beginTransaction();
		this.session.save(operation);
		this.session.getTransaction().commit();
		this.operations.put(new String(operation.getName(), 0, operation.getName().length, "UTF-8"), operation);
		System.out.println(this.logClass.getSimpleName() + "\tnew operation id: " + operation.getId());
	}
	
	private void addLog(Log log) {
		this.session.beginTransaction();
		this.session.save(log);
		this.session.getTransaction().commit();
		System.out.println(this.logClass.getSimpleName() + "\t" + log.getExecuted() + "\t" + log.getOperation().getType().getName());
	}
	
	public Timestamp getTimestamp() {
		return this.timestamp;
	}

}
